---
title: Guide Documents > Core Library > TypedFormData
---
import { Tabs } from 'nextra/components'

## Outline
```typescript filename="@nestia/core" showLineNumbers
export namespace TypedFormData {
  export function Body<Multer extends IMulterBase>(
    factory: () => Multer | Promise<Multer>
  ): ParameterDecorator;
  export type IMulterBase = ExpressMulter.Multer | FastifyMulter.Multer;
}
```

Request body decorator of `multipart/form-data`.

`@TypedFormData.Body()` is a request body decorator function, for the `multipart/form-data` content type. It is useful for file uploading with additional data, and automatically casts property type following its DTO definition, performing the type validation.

As you can see from the below code, `@TypedFormData.Body()` function is much easier and type safer than `@UploadFile()` of NestJS. Also, if you're considering the [SDK library](../sdk/sdk) generation, only `@TypedFormData.Body()` is supported. Therefore, I recommend you to utilize `@TypedFormData.Body()` instead of the `@UploadFile()` function.

Of course, as every features of `nestia` does, you don't need to define any extra schema definition for the [Swagger Documents](../sdk/swagger) generation. `@nestia/sdk` and `@TypedFormData.Body()` will do everything just by analyzing your TypeScript types and codes.

<Tabs items={[
  <code>@TypedFormData.Body()</code>, 
  <code>@UploadFile()</code>
]}>
  <Tabs.Tab>
```typescript filename="nestia/MultipartController.ts" showLineNumbers {8}
import { TypedFormData, TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";
import Multer from "multer";

@Controller("bbs/articles")
export class BbsArticlesController {
  @TypedRoute.Post()
  public async create(
    @TypedFormData.Body(() => Multer()) input: IBbsArticleCreate,
  ): Promise<void> {
    input;
  }
}

export interface IBbsArticleCreate {
  title: string;
  body: string | null;
  thumbnail?: File | undefined;
  files: File[];
  tags: string[];
}
```
  </Tabs.Tab>
  <Tabs.Tab>
```typescript filename="nestjs/multipart.controller.ts" showLineNumbers {16-55}
import { 
  Body, 
  Controller, 
  Post, 
  UploadedFile, 
  UploadedFiles, 
  UseInterceptors 
} from "@nestjs/common";
import { ApiBody, ApiConsumes } from "@nestjs/swagger";
import { FileFieldsInterceptor } from "@nestjs/platform-express";
import { IsArray, IsOptional, IsString } from "class-validator";

@Controller("bbs/articles")
export class BbsArticlesController {
  @Post()
  @ApiConsumes("multipart/form-data")
  @UseInterceptors(
    FileFieldsInterceptor([
      { name: "thumbnail", maxCount: 1 },
      { name: "files" },
    ]),
  )
  @ApiBody({
    schema: {
      type: "object",
      properties: {
        title: {
          type: "string",
        },
        body: {
          type: "string",
          nullable: true,
        },
        thumbnail: {
          type: "string",
          format: "binary",
        },
        files: {
          type: "array",
          items: {
            type: "string",
            format: "binary",
          },
        },
      },
      required: [
        "id", 
        "title", 
        "body", 
        "files",
        "tags",
        "created_at",
      ],
    },
  })
  public async create(
    @Body() input: BbsArticleCreateDto,
    @UploadedFiles() binary: {
      thumbnail?: Express.Multer.File[];
      files: Express.Multer.File[];
    },
  ): Promise<void> {
    input;
    binary;
  }
}

export class BbsArticleCreateDto {
  @IsString()
  title: string;

  @IsString()
  @IsOptional()
  body: string | null;

  @IsArray({ each: true })
  @IsString()
  tags: string[];
}
```
  </Tabs.Tab>
</Tabs>




## How to use
<Tabs
  items={[
    <code>IBbsArticleCreate.ts</code>,
    <code>BbsArticlesController.ts</code>,
    <code>BbsArticlesController.js</code>,
  ]}
  defaultIndex={1}>
  <Tabs.Tab>
```typescript copy filename="IBbsArticleCreate.ts" showLineNumbers
export interface IBbsArticleCreate {
  title: string;
  body: string | null;
  thumbnail?: File | undefined;
  files: File[];
  tags: string[];
}
```
  </Tabs.Tab>
  <Tabs.Tab>
```typescript copy filename="BbsArticlesController.ts" showLineNumbers {10}
import { TypedFormData, TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";
import Multer from "multer";

import { IBbsArticleCreate } from "./IBbsArticleCreate";

@Controller("bbs/articles")
export class BbsArticlesController {
  @TypedRoute.Post()
  public async create(
    @TypedFormData.Body(() => Multer()) input: IBbsArticleCreate,
  ): Promise<void> {
    input;
  }
}
```
  </Tabs.Tab>
  <Tabs.Tab>
```javascript copy filename="BbsArticlesController.js" showLineNumbers {114-247}
"use strict";
var __decorate =
  (this && this.__decorate) ||
  function (decorators, target, key, desc) {
    var c = arguments.length,
      r =
        c < 3
          ? target
          : desc === null
          ? (desc = Object.getOwnPropertyDescriptor(target, key))
          : desc,
      d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
      r = Reflect.decorate(decorators, target, key, desc);
    else
      for (var i = decorators.length - 1; i >= 0; i--)
        if ((d = decorators[i]))
          r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
  };
var __metadata =
  (this && this.__metadata) ||
  function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
      return Reflect.metadata(k, v);
  };
var __param =
  (this && this.__param) ||
  function (paramIndex, decorator) {
    return function (target, key) {
      decorator(target, key, paramIndex);
    };
  };
var __awaiter =
  (this && this.__awaiter) ||
  function (thisArg, _arguments, P, generator) {
    function adopt(value) {
      return value instanceof P
        ? value
        : new P(function (resolve) {
            resolve(value);
          });
    }
    return new (P || (P = Promise))(function (resolve, reject) {
      function fulfilled(value) {
        try {
          step(generator.next(value));
        } catch (e) {
          reject(e);
        }
      }
      function rejected(value) {
        try {
          step(generator["throw"](value));
        } catch (e) {
          reject(e);
        }
      }
      function step(result) {
        result.done
          ? resolve(result.value)
          : adopt(result.value).then(fulfilled, rejected);
      }
      step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
  };
Object.defineProperty(exports, "__esModule", { value: true });
exports.BbsArticlesController = void 0;
const core_1 = require("@nestia/core");
const common_1 = require("@nestjs/common");
let BbsArticlesController = class BbsArticlesController {
  create(input) {
    return __awaiter(this, void 0, void 0, function* () {
      input;
    });
  }
};
exports.BbsArticlesController = BbsArticlesController;
__decorate(
  [
    core_1.TypedRoute.Post({
      type: "assert",
      assert: (input) => {
        const assert = (input) => {
          const __is = (input) => {
            return null !== input && undefined === input;
          };
          if (false === __is(input))
            ((input, _path, _exceptionable = true) => {
              const $guard = core_1.TypedRoute.Post.guard;
              return (
                (null !== input ||
                  $guard(true, {
                    path: _path + "",
                    expected: "undefined",
                    value: input,
                  })) &&
                (undefined === input ||
                  $guard(true, {
                    path: _path + "",
                    expected: "undefined",
                    value: input,
                  }))
              );
            })(input, "$input", true);
          return input;
        };
        const stringify = (input) => {
          return undefined;
        };
        return stringify(assert(input));
      },
    }),
    __param(
      0,
      core_1.TypedFormData.Body({
        files: [
          {
            name: "thumbnail",
            limit: 1,
          },
          {
            name: "files",
            limit: null,
          },
        ],
        validator: {
          type: "assert",
          assert: (input) => {
            const decode = (input) => {
              var _a;
              const $string = core_1.TypedFormData.Body.string;
              const $file = core_1.TypedFormData.Body.file;
              const output = {
                title: $string(input.get("title")),
                body: $string(input.get("body")),
                thumbnail:
                  (_a = $file(input.get("thumbnail"))) !== null && _a !== void 0
                    ? _a
                    : undefined,
                files: input.getAll("files").map((elem) => $file(elem)),
                tags: input.getAll("tags").map((elem) => $string(elem)),
              };
              return output;
            };
            const assert = (input) => {
              const __is = (input) => {
                const $io0 = (input) =>
                  "string" === typeof input.title &&
                  (null === input.body || "string" === typeof input.body) &&
                  (undefined === input.thumbnail ||
                    input.thumbnail instanceof File) &&
                  Array.isArray(input.files) &&
                  input.files.every((elem) => elem instanceof File) &&
                  Array.isArray(input.tags) &&
                  input.tags.every((elem) => "string" === typeof elem);
                return (
                  "object" === typeof input && null !== input && $io0(input)
                );
              };
              if (false === __is(input))
                ((input, _path, _exceptionable = true) => {
                  const $guard = core_1.TypedFormData.Body.guard;
                  const $ao0 = (input, _path, _exceptionable = true) =>
                    ("string" === typeof input.title ||
                      $guard(_exceptionable, {
                        path: _path + ".title",
                        expected: "string",
                        value: input.title,
                      })) &&
                    (null === input.body ||
                      "string" === typeof input.body ||
                      $guard(_exceptionable, {
                        path: _path + ".body",
                        expected: "(null | string)",
                        value: input.body,
                      })) &&
                    (undefined === input.thumbnail ||
                      input.thumbnail instanceof File ||
                      $guard(_exceptionable, {
                        path: _path + ".thumbnail",
                        expected: "(File | undefined)",
                        value: input.thumbnail,
                      })) &&
                    (((Array.isArray(input.files) ||
                      $guard(_exceptionable, {
                        path: _path + ".files",
                        expected: "Array<File>",
                        value: input.files,
                      })) &&
                      input.files.every(
                        (elem, _index1) =>
                          elem instanceof File ||
                          $guard(_exceptionable, {
                            path: _path + ".files[" + _index1 + "]",
                            expected: "File",
                            value: elem,
                          }),
                      )) ||
                      $guard(_exceptionable, {
                        path: _path + ".files",
                        expected: "Array<File>",
                        value: input.files,
                      })) &&
                    (((Array.isArray(input.tags) ||
                      $guard(_exceptionable, {
                        path: _path + ".tags",
                        expected: "Array<string>",
                        value: input.tags,
                      })) &&
                      input.tags.every(
                        (elem, _index2) =>
                          "string" === typeof elem ||
                          $guard(_exceptionable, {
                            path: _path + ".tags[" + _index2 + "]",
                            expected: "string",
                            value: elem,
                          }),
                      )) ||
                      $guard(_exceptionable, {
                        path: _path + ".tags",
                        expected: "Array<string>",
                        value: input.tags,
                      }));
                  return (
                    ((("object" === typeof input && null !== input) ||
                      $guard(true, {
                        path: _path + "",
                        expected: "IBbsArticleCreate",
                        value: input,
                      })) &&
                      $ao0(input, _path + "", true)) ||
                    $guard(true, {
                      path: _path + "",
                      expected: "IBbsArticleCreate",
                      value: input,
                    })
                  );
                })(input, "$input", true);
              return input;
            };
            const output = decode(input);
            return assert(output);
          },
        },
      }),
    ),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise),
  ],
  BbsArticlesController.prototype,
  "create",
  null,
);
exports.BbsArticlesController = BbsArticlesController = __decorate(
  [(0, common_1.Controller)("bbs/articles")],
  BbsArticlesController,
);
//# sourceMappingURL=BbsArticlesController.js.map
```
  </Tabs.Tab>
</Tabs>

Just call `@TypedFormData.Body()` function on the request body parameter, that's all.

`Nestia` will analyze your type (`IBbsArticleCreate`), and writes optimal code for the target type, in the compilation level. If you click the "Compiled JavaScript File" tab of above, you can see the optimal transformation and validation code. 

Such optimization is called AOT (Ahead of Time) compilation, and it is the secret of `@TypedFormData.Body`.

By the way, if you're using `fastify`, you have to setup `fastify-multer` and configure like below when composing the NestJS application. If you don't do it, `@TypedFormData.Body()` will not work properly, and throw 500 internal server error when `Blob` or `File` type being utilized.

```typescript filename="fastify-nestjs showLineNumbers {1, 11}
// main.ts
import { NestFactory } from "@nestjs/core";
import { 
  FastifyAdapter, 
  NestFastifyApplication 
} from "@nestjs/platform-fastify";
import FastifyMulter from "fastify-multer";

export async function main() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  app.register(FastifyMulter.contentParser as any);
  await app.listen(3000);
}

// BbsArticlesController.ts
import { TypedFormData, TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";
import FastifyMulter from "fastify-multer";

import { IBbsArticleCreate } from "./IBbsArticleCreate";

@Controller("bbs/articles")
export class BbsArticlesController {
  @TypedRoute.Post()
  public async create(
    @TypedFormData.Body(() => FastifyMulter()) input: IBbsArticleCreate,
  ): Promise<void> {
    input;
  }
}
```

## Special Tags
You can enhance validation logic, of `@TypedFormData.Body()`, through comment tags.

You know what? `@TypedFormData.Body()` utilizes [`typia.assert<T>()`](https://typia.io/docs/validators/assert) function for form data validation, and the [`typia.assert<T>()`](https://typia.io/docs/validators/assert) function supports additional type checking logics through comment tags. For reference, "Type Tag" means a intersection type with atomic type and special tag type of `typia` like `number & tags.Type<"uint32">`, and "Comment Tag" means a comment starting from `@` symbol following `@${name} ${value}` format.

With those type and comment tags, you can add additional validation logics. If you want to add a custom validation logic, you also can do it. Read below Guide Documents of [typia](https://typia.io), and see the example code. You may understand how to utilize such type and comment tags, in a few minutes.

  - [**`typia` > Validators > Custom Tags**](https://typia.io/docs/validators/tags/)
    - [Outline](https://typia.io/docs/validators/tags/#outline)
    - [Type Tags](https://typia.io/docs/validators/tags/#type-tags)
    - [Comment Tags](https://typia.io/docs/validators/tags/#comment-tags)
    - [Customization](https://typia.io/docs/validators/tags/#customization)

<Tabs items={['TypeScript Source Code', 'Compiled JavaScript File']}>
  <Tabs.Tab>
```typescript copy filename="examples/src/is-special-tags.ts" showLineNumbers {3}
import typia, { tags } from "typia";

export const checkSpecialTag = typia.createIs<SpecialTag>();

interface SpecialTag {
  int32: number & tags.Type<"int32">;
  range?: number & tags.ExclusiveMinimum<19> & tags.Maximum<100>;
  minLength: string & tags.MinLength<3>;
  pattern: string & tags.Pattern<"^[a-z]+$">;
  date: null | (string & tags.Format<"date">);
  ip: string & (tags.Format<"ipv4"> | tags.Format<"ipv6">);
  uuids: Array<string & tags.Format<"uuid">> &
    tags.MinItems<3> &
    tags.MaxItems<100>;
}
```
  </Tabs.Tab>
  <Tabs.Tab>
```javascript filename="examples/bin/is-special-tags.js" showLineNumbers {10-45}
"use strict";
var __importDefault =
  (this && this.__importDefault) ||
  function (mod) {
    return mod && mod.__esModule ? mod : { default: mod };
  };
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkSpecialTag = void 0;
const typia_1 = __importDefault(require("typia"));
const checkSpecialTag = (input) => {
  const $io0 = (input) =>
    "number" === typeof input.int32 &&
    Math.floor(input.int32) === input.int32 &&
    -2147483648 <= input.int32 &&
    input.int32 <= 2147483647 &&
    (undefined === input.range ||
      ("number" === typeof input.range &&
        19 < input.range &&
        input.range <= 100)) &&
    "string" === typeof input.minLength &&
    3 <= input.minLength.length &&
    "string" === typeof input.pattern &&
    /^[a-z]+$/.test(input.pattern) &&
    (null === input.date ||
      ("string" === typeof input.date &&
        /^(d{4})-(d{2})-(d{2})$/.test(input.date))) &&
    "string" === typeof input.ip &&
    (/^(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(
      input.ip,
    ) ||
      /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/.test(
        input.ip,
      )) &&
    Array.isArray(input.uuids) &&
    3 <= input.uuids.length &&
    input.uuids.length <= 100 &&
    input.uuids.every(
      (elem) =>
        "string" === typeof elem &&
        /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i.test(
          elem,
        ),
    );
  return "object" === typeof input && null !== input && $io0(input);
};
exports.checkSpecialTag = checkSpecialTag;
```
  </Tabs.Tab>
</Tabs>




## Restriction
When using `@TypedFormData.Body()`, you've to follow such restriction.

At first, type of `@TypedFormData.Body()` must be a pure **object type**. It does not allow union type. Also, nullable and undefindable types are not allowed, either. Note that, query parameter type must be a sole **object type** without any extra definition.

At next, type of properties must be **atomic**, `Blob`, `File` or array of them. In the atomic type case, the atomic type allows both nullable and undefindable types. However, mixed union atomic type like `string | number` or `"1" | "2" | 3` are not allowed. Also, the array type does not allow both nullable and undefindable types, either.

  - `boolean`
  - `number`
  - `bigint`
  - `string`
  - `Blob`
  - `File`

```typescript filename="SomeFormDataDto.ts" showLineNumbers
export interface SomeFormDataDto {
  //----
  // ATOMIC OR FILE TYPES
  //----
  // ALLOWED
  boolean: boolean;
  number: number;
  string: string;
  bigint: bigint;
  optional_number?: number;
  nullable_string: string | null;
  literal_union: "A" | "B" | "C" | "D";
  blob: Blob;
  file: File;

  // NOT ALLOWED
  mixed_union: string | number | boolean;
  mixed_literal: "A" | "B" | 3;

  //----
  // ARRAY TYPES
  //----
  // ALLOWED
  nullable_element_array: (string | null)[];
  string_array: string[];
  number_array: number[];
  literal_union_array: ("A" | "B" | "C")[];
  literal_tuple: ["A", "B", "C"];
  blobs: Blob[];
  files: File[];

  // NOT ALLOWED
  optional_element_array: (string | undefined)[];
  optional_array: string[] | undefined;
  nullable_array: string[] | null;
  union_atomic_array: (string | number)[];
  mixed_literal_array: ("A", "B", 3)[];
  mixed_tuple: ["A", "B", 3];
}
```



